#include <stdio.h>
#include <unistd.h>
#include <pthread.h>
#include <stdint.h>
#include <string.h>
#include <sys/socket.h>
#include <sys/types.h>
#include <arpa/inet.h>
#include <sys/stat.h>
#include <ctype.h>
#include <stdlib.h>
#include <sys/wait.h>


int get_line(int client, char *buf, int size)
{
	char ch = 0;
	int i = 0;
	int n = 0;

	while(i < size -1 && ch != '\n')
	{
		n = recv(client, &ch, 1, 0);
		if(n > 0)
		{
			if(ch == '\r')
			{
				n = recv(client, &ch, 1, MSG_PEEK);
				if(n > 0 && ch == '\n')
				{
					recv(client, &ch, 1, 0);
				}
				else
				{
					ch = '\n';
				}
			}
			buf[i++] = ch;
		}
		else
		{
			ch = '\n';
		}
	}
	buf[i] = '\0';
	return i;
}

void error_die(const char * str)
{
	perror(str);
	exit(1);
}

void bad_request(int client)
{
	char buf[1024] = {0};

	sprintf(buf, "HTTP/1.1 400 BAD REQUEST\r\n");
	send(client, buf, sizeof(buf), 0);
	sprintf(buf, "Content-type: text/html\r\n");
	send(client, buf, sizeof(buf), 0);
	sprintf(buf, "\r\n");
	send(client, buf, sizeof(buf), 0);
	sprintf(buf, "<p>Your browser sent a bad request, ");
	send(client, buf, sizeof(buf), 0);
	sprintf(buf, "such as a POST whithout a Content-Length.\r\n");
	send(client, buf, sizeof(buf), 0);
}

void cannot_execute(int client)
{
	char buf[1024] = {0};

	sprintf(buf, "HTTP/1.1 500 BAD REQUEST\r\n");
	send(client, buf, sizeof(buf), 0);
	sprintf(buf, "Content-type: text/html\r\n");
	send(client, buf, sizeof(buf), 0);
	sprintf(buf, "\r\n");
	send(client, buf, sizeof(buf), 0);
	sprintf(buf, "<P>Error prohibited CGI execution.\r\n</P>");
	send(client, buf, sizeof(buf), 0);
}

void cat(int client, FILE* resource)
{
	char buf[1024] = {0};

	do
	{
		if(fgets(buf, sizeof(buf), resource) == NULL)
		{
			perror("cat error:fgets error");
			break;
		}
		if(send(client, buf,sizeof(buf), 0)	< 0)
		{
			break;
		}
	}while(!feof(resource));
}

void not_found(int client)
{
	char buf[1024] = {0};

	sprintf(buf, "HTTP/1.0 404 NOT FOUND\r\n");
	send(client, buf, strlen(buf), 0);
	sprintf(buf, "Server: jdbhttpd/0.1.0\r\n");
	send(client, buf, strlen(buf), 0);
	sprintf(buf, "Content-Type: text/html\r\n");
	send(client, buf, strlen(buf), 0);
	sprintf(buf, "\r\n");
	send(client, buf, strlen(buf), 0);
	sprintf(buf, "<HTML><TITLE>Not Found</TITLE>\r\n");
	send(client, buf, strlen(buf), 0);
	sprintf(buf, "<BODY><P>The server could not fulfill\r\n");
	send(client, buf, strlen(buf), 0);
	sprintf(buf, "your request because the resource specified\r\n");
	send(client, buf, strlen(buf), 0);
	sprintf(buf, "is unavailable or nonexistent.\r\n");
	send(client, buf, strlen(buf), 0);
	sprintf(buf, "</BODY></HTML>\r\n");
	send(client, buf, strlen(buf), 0);
}

void headers(int client, const char *filename)
{
	char buf[1024] = {0};
	(void)filename;
	strcpy(buf, "HTTP/1.1 200 OK\r\n");
	send(client, buf, strlen(buf), 0);
	strcpy(buf, "Server: jdbhttpd/0.1.0\r\n");
	send(client, buf, strlen(buf), 0);
	sprintf(buf, "Content-Type: text/html; charset=UTF-8\r\n");
	send(client, buf, strlen(buf), 0);
	sprintf(buf, "\r\n");
	send(client, buf, strlen(buf), 0);
}

void unimplemented(int client)
{
	char buf[1024] = {0};

	sprintf(buf, "HTTP/1.1 501 Method Not Implemented \r\n");
	send(client, buf, strlen(buf), 0);
	sprintf(buf, "Server: jdbhttpd/0.1.0\r\n");
	send(client, buf, strlen(buf), 0);
	sprintf(buf, "Content-Type: text/html\r\n");
	send(client, buf, strlen(buf), 0);
	sprintf(buf, "\r\n");
	send(client, buf, strlen(buf), 0);
	sprintf(buf, "<HTML><HEAD><TITLE>Method Not Implemented\r\n");
	send(client, buf, strlen(buf), 0);
	sprintf(buf, "</TITLE></HEAD>\r\n");
	send(client, buf, strlen(buf), 0);
	sprintf(buf, "<BODY><P>HTTP request method not supported.\r\n");
	send(client, buf, strlen(buf), 0);
	sprintf(buf, "</BODY></HTML>\r\n");
	send(client, buf, strlen(buf), 0);
}

void serve_file(int client, const char *filename)
{
	FILE *resource = fopen(filename, "r");
	int numchars = 1;
	char buf[1024] = {0};

	while(numchars > 0 && strcmp("\n", buf))
		numchars = get_line(client, buf, sizeof(buf));
	if(resource == NULL)
		not_found(client);
	else
	{
		headers(client, filename);
		cat(client, resource);
	}
	fclose(resource);
}

void execute_cgi(int client, const char* path, const char* method, const char* query_string)
{
	char buf[1024] = {0};
	int cgi_output[2] = {0};
	int cgi_input[2] = {0};
	pid_t pid = 0;
	int status = 0;
	int i = 0;
	char ch = 0;
	int numchars = 1;
	int content_length = -1;
	if(strcasecmp(method, "GET") == 0)
	{
		while(numchars > 0 && strcmp("\n", buf))
			numchars = get_line(client, buf, sizeof(buf));
	}
	else
	{
		while(get_line(client, buf, sizeof(buf)) > 0 && strcmp("\n", buf))
		{
			buf[15] = '\0';
			if(!strcasecmp(buf, "Content-Length:"))
				content_length = atoi(&buf[16]);
		}
		if(content_length == -1)
		{
			bad_request(client);
			return;
		}
	}
	sprintf(buf, "HTTP/1.1 200 OK\r\n");
	send(client, buf, strlen(buf), 0);

	if(pipe(cgi_input) < 0)
	{
		cannot_execute(client);
		return;
	}
	if(pipe(cgi_output) < 0)
	{
		cannot_execute(client);
		return;
	}
	pid = fork();
	if(pid < 0)
	{
		cannot_execute(client);
		return;
	}
	else if(pid == 0)
	{
		char meth_env[255] = {0};
		char query_env[255] = {0};
		char length_env[255] = {0};

		dup2(cgi_output[1], STDOUT_FILENO);
		dup2(cgi_input[0], STDIN_FILENO);
		close(cgi_output[0]);
		close(cgi_input[1]);
		sprintf(meth_env, "REQUEST_METHOD=%s", method);
		putenv(meth_env);
		if(!strcasecmp(method, "GET"))
		{
			sprintf(query_env, "QUERY_STRING=%s", query_string);
			putenv(query_env);
		}
		else
		{
			sprintf(length_env, "CONTENT_LENGTH=%d", content_length);
			putenv(length_env);
		}
		execl(path, path, NULL);
		exit(0);
	}
	else
	{
		close(cgi_input[0]);
		close(cgi_output[1]);
		if(strcasecmp(method, "POST"))
		{
			for(i = 0; i < content_length; ++i)
			{
				recv(client, &ch, 1, 0);
				write(cgi_input[1], &ch, 1);
			}
		}
		while(read(cgi_output[0], &ch, 1) > 0)
			write(client, &ch, 1);
		close(cgi_input[1]);
		close(cgi_output[0]);
		waitpid(pid, &status, 0);
	}
}


void * accept_request(void * client_sock)
{
	char buf[1024] = {0};
	int numchars = 0;
	char method[255] = {0};
	char url[255] = {0};
	char path[512] = {0};
	size_t i = 0, j = 0;
	struct stat st;
	int cgi = 0;
	int client = (int)client_sock;

	char *query_string = NULL;
	numchars = get_line(client, buf, sizeof(buf));
	while(!isspace((int)buf[j]) && (i < sizeof(method) -1))
	{
		method[i] = buf[j];
		++i;
		++j;
	}
	method[i] = '\0';
	if(strcasecmp(method, "POST") && strcasecmp(method, "GET"))
	{
		unimplemented(client);
		return NULL;
	}
	if(strcasecmp(method, "POST") == 0)
	{
		cgi = 1;
	}
	i = 0;
	while(isspace((int)buf[j] && (j < sizeof(buf))))
		j++;
	while(!isspace((int)buf[j]) && (j < sizeof(buf)) && (i < sizeof(url) - 1))
	{
		url[i++] = buf[j]++;
	}
	url[i] = '\0';
	if(!strcasecmp(method, "GET"))
	{
		query_string = url;
		query_string = strchr(query_string, '?');
		if(query_string)
		{
			cgi = 1;
			*query_string = '\0';
			++query_string;
		}
	}
	sprintf(path, "htdocs%s", url);
	if(path[strlen(path) - 1] == '/')
	{
		strcat(path, "index.html");
	}
	if(stat(path, &st) < 0)
	{
		while((numchars > 0) && strcmp("\n", buf))
		{
			numchars = get_line(client, buf, sizeof(buf));
		}
		not_found(client);
	}
	else
	{
		if((st.st_mode & S_IFMT) == S_IFDIR)
			strcat(path, "/index.html");
		if((st.st_mode & S_IXUSR) || (st.st_mode & S_IXGRP) || (st.st_mode & S_IXOTH))
			cgi = 1;
		if(!cgi)
			serve_file(client, path);
		else
			execute_cgi(client, path, method, query_string);
	}
	close(client);
	return NULL;
}

int startup(u_short *port)
{
	int httpd = 0;
	struct sockaddr_in name;

	httpd = socket(AF_INET, SOCK_STREAM, 0);
	if (httpd < 0)
		error_die("socket");
	memset(&name, 0, sizeof(name));
	name.sin_family = AF_INET;
	name.sin_addr.s_addr = htonl(INADDR_ANY);
	name.sin_port = htons(*port);

	if(bind(httpd, (struct sockaddr*)&name, sizeof(name)) < 0)
		error_die("bind");
	if(*port == 0)
	{
		int namelen = sizeof(name);
		if(getsockname(httpd, (struct sockaddr*)&name, &namelen) < 0)
			error_die("getsockname");
		*port = ntohs(name.sin_port);
	}
	if(listen(httpd, 10) < 0)
		error_die("listen");
	return httpd;
}




int main(int argc, char **argv)
{
	int server_sock = -1;
	u_short port = 0;
	int client_sock = -1;
	struct sockaddr_in client_name;
	int client_name_len = sizeof(client_name);
	pthread_t newthread = 0;
	pthread_attr_t attr ;

	server_sock = startup(&port);
	printf("httpd running on port %d\n", port);

	memset(&client_name, 0, client_name_len);
	if(pthread_attr_init(&attr) < 0)
		error_die("pthread attr init");
	if(pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_DETACHED) < 0)
		error_die("pthread attr set detached state");
	while(1)
	{
		client_sock = accept(server_sock, (struct sockaddr*) &client_name, &client_name_len);
		if(client_sock == -1)
			error_die("accept");
		if(pthread_create(&newthread, &attr, accept_request, (void*) client_sock) < 0)
			perror("pthread_create");
	}
	close(server_sock);
	return 0;
}
